import { io, Socket } from "socket.io-client";
import * as Y from "yjs";
import {
  Awareness,
  encodeAwarenessUpdate,
  applyAwarenessUpdate,
} from "y-protocols/awareness";

export class CustomSocketProvider {
  private socket: Socket;
  public awareness: Awareness;

  constructor(serverUrl: string, roomName: string, ydoc: Y.Doc) {
    // 创建一个 socket 连接，指定使用 websocket 的连接方式
    this.socket = io(serverUrl, {
      transports: ["websocket"],
    });

    // 初始化一个 Awareness 实例
    this.awareness = new Awareness(ydoc);

    // 接下来就是监听一系列的事件

    // connect 事件会在成功连接上 websocket 服务器的时候触发
    this.socket.on("connect", () => {
      // 触发一个 websocket 服务器上面的一个 join-room 事件并且将 roomName 传递过去
      // 这意味着回头写 websocket 服务器的时候，需要监听这么一个事件
      this.socket.emit("join-room", roomName);
    });

    // 监听 sync-doc 事件，该事件会在 websocket 服务器推送过来完整的文档数据的时候会触发
    // 那么要做的事情：同步我当前客户端的 yjs 文档状态
    // 应用场景：文档初次同步
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    this.socket.on("sync-doc", (update: ArrayBuffer) => {
      Y.applyUpdate(ydoc, new Uint8Array(update));
    });

    // websocket 服务器推送过来文档的增量更新内容的时候触发
    // 这里意味着回头 websocket 服务器可以触发这么一个事件
    this.socket.on("update", (update: ArrayBuffer) => {
      Y.applyUpdate(ydoc, new Uint8Array(update));
    });

    // 当本地文档有变化的时候，需要将 update 进行一个广播
    ydoc.on("update", (update: Uint8Array) => {
      // 这里触发了 update 事件，意味着websocket服务器需要监听update这个一个事件
      this.socket.emit("update", update.buffer);
    });

    // 在进行协同的时候，除了需要同步文档的内容，还需要同步一些额外的信息
    // 谁在线？谁在输入？谁的光标在什么位置？
    // 这些信息虽然是临时的，但是也很重要，需要进行同步
    // 当本地用户的光标信息发生变化，会触发 update 事件
    this.awareness.on(
      "update",
      ({
        added,
        updated,
        removed,
      }: {
        added: number[]; // 新增的用户的 id 列表
        updated: number[]; // 已存在的用户状态发生了变化（例如光标位置变了）
        removed: number[]; // 离开了协作编辑器的用户的 id
      }) => {
        // 需要将变化的状态信息打包成二进制数据，方便网络传输
        const update = encodeAwarenessUpdate(this.awareness, [
          ...added,
          ...updated,
          ...removed,
        ]);
        // 发送给websocket服务器
        // 这里同样意味着回头我们在写 websocket 服务器的时候，需要监听这么一个 awareness-update 的事件
        this.socket.emit("awareness-update", update);
      }
    );

    // 同理，websocket 服务器给你推送过来的其他用户的新的光标信息
    // 你这边也需要更新
    this.socket.on("awareness-update", (update: ArrayBuffer) => {
      applyAwarenessUpdate(
        this.awareness,
        new Uint8Array(update),
        this.socket.id
      );
    });
  }

  /**
   * 销毁方法，断开websocket连接的时候调用
   */
  destroy() {
    this.socket.disconnect();
  }
}
